<?php

class CABLEconfig
{
	public	$values	= array();
	public	$error	= false;
	public	$messages = array();
	
	public	function __construct()
	{
		if (file_exists('./config.ini'))
		{
			if (($this->values = parse_ini_file('./config.ini',true)) === false)
			{
				$this->error = true;
				$this->messages[0][] = CABLE_ERROR_CONFIG_UNREADABLE;
				$this->values['ErrorThreshold'] = 2;
				return null;
			}
			
			/* replacements */
			if ($this->values['TablePrefix'] == '')
				$this->values['TablePrefix'] = 'default';
		}
		else
		{
			$this->error = true;
			$this->messages[0][] =	CABLE_ERROR_CONFIG_MISSING;
			$this->values['ErrorThreshold'] = 2;
		}
					
		return null;
	}
}

class CABLEdb
{
	private $config		= null;
	private	$server		= null;
	private	$username	= null;
	private	$password	= null;
	private	$db_name	= null;
	
	public	$db_link	= null;
	public	$error		= false;
	public	$messages	= array();
	
	public	function Connect($server,$username,$password,$database)
	{
		$this->db_link = mysql_connect($server,$username,$password);
		if ((!mysql_select_db($database)) || (!$this->db_link))
		{
			$this->messages[0][] = CABLE_ERROR_DATABASE_NC;
			return false;
		}
		
		$this->server = $server;
		$this->username = $username;
		$this->password = $password;
		$this->database = $database;
		
		return true;
	}
	
	public	function Close()
	{
		if(mysql_close($this->db_link))
			return true;
			
		$this->messages[0][] = CABLE_ERROR_DATABASE_CLOSE;
		return false;
	}
}

class CABLEuser
{
	public	$timestamp;
	public	$values		= array();
	public	$read		= array();
	public	$logged_in	= false;
	public	$is_admin	= false;
	
	public	$error		= false;
	public	$messages	= array();
		
	public	function __construct($config,$db)
	{
		$lookup_user = false;
		if (($_REQUEST['username']) || ($_REQUEST['password']))
		{
			$this->values[username] = urldecode($_REQUEST[username]);
			$this->values[password] = md5($this->values[username].urldecode($_REQUEST[password]));
			$lookup_user = true;
		}
		elseif ($_COOKIE['CABLE_auth'])
		{
			list($this->values[username],$this->values[password]) = explode(',', urldecode($_COOKIE['CABLE_auth']));
			$lookup_user = true;
		}
		
		/* should we attempt to look up user record? */
		if ($lookup_user)
		{
			/* strip potential uglies */
			$this->values[username] = str_replace('"','',str_replace("'",'',$this->values[username]));
			$this->values[password] = str_replace('"','',str_replace("'",'',$this->values[password]));
			
			/* try and find the user in the database */
			$table = "cable_".$config->values['TablePrefix']."_users";
			$query = "SELECT * FROM $table WHERE (username = '".$this->values[username]."') AND (password  = '".$this->values[password]."') LIMIT 1";
			if ($result = mysql_query($query,$db->db_link))
			{
				if(mysql_num_rows($result) > 0)
				{
					$row = mysql_fetch_assoc($result);
					foreach($row as $key => $value)
						$this->values[$key] = $value;
					
					/* unserialize any values in the data field and append them */
					//$this->values = @array_merge($this->values,unserialize($this->values['data']));
					
					/* check on admin level - will work on later */
					$this->logged_in = true;
					if ($this->values[adminlevel] != '')
						$this->is_admin = true;
					
					/* break up first and last names, for convience later on */
					list($this->values[firstname],$this->values[lastname]) = explode(' ',$this->values[fullname]);
				}
				else // failed to log in
					$this->messages[2][] = str_replace('<id>',$this->values[username],CABLE_ERROR_USER_RECORD);
			}
			else
				$this->messages[1][] = str_replace('<id>',mysql_error($db->db_link),CABLE_ERROR_DATABASE_REQUEST);
		}
		else // anonymous user
		{
			$this->values[username] = 'Anonymous';
			$this->values[fullname] = 'Anonymous';
		}
		
		/* get requested timestamp */
		if ($_REQUEST['t'])
			$this->timestamp = (int) $_REQUEST['t'];
		else
			$this->timestamp = time();
		
		return null;
	}
	
	public	function SaveCookies()
	{
		$timeout_value = time() + BLOG_COOKIE_TIMEOUT;
				
		/* do authentication */
		$authentication_string	= $this->values[username].','.$this->values[password];
		setrawcookie('CABLE_auth',urlencode($authentication_string),$timeout_value,BLOG_COOKIE_PATH);
		
		$this->messages[3][] = CABLE_COOKIE_SAVED;
		
		return null;
	}
	
	public	function DeleteCookies()
	{
		setcookie('CABLE_auth','',time() -3600);
		$this->messages[3][] = CABLE_COOKIE_DELETED;
		
		return null;
	}
	
	public	function LogOut()
	{
		setcookie('CABLE_auth', false);
		
		$this->logged_in = false;
		$this->is_admin = false;
		$this->values[adminlevel] = 0;
		$this->values[username] = 'Anonymous';
		$this->values[fullname] = 'Anonymous';
		$this->values[password] = '';
		
		return null;
	}
}

class CABLEtemplate
{
	private	$id			= null;
	private $config		= null;
	
	public	$original	= null;
	public	$template	= null;
	public	$cache		= null;
	public	$error		= false;
	public	$messages	= array();
	
	public	function __construct(&$config=false,$db=false,$theme=false,$id=false)
	{
		/* extract the configuration variables, if they exist */
		if (is_object($config))
			$this->config = $config->values;
		elseif(is_array($config))
			$this->config = $config;
				
		if (($config !== false) && ($db !== false) && ($theme !== false))
		{
			$this->id = $id;
				
			$table = "cable_".$this->config['TablePrefix']."_templates";
			$query = "SELECT template,compatible_version FROM $table WHERE (name='$id') AND (theme='$theme') LIMIT 1";
			if($result = mysql_query($query,$db->db_link))
			{
				if (mysql_num_rows($result) > 0)
				{
					$row = mysql_fetch_array( $result, MYSQL_ASSOC );
					if (CABLE_checkVersion(CABLE_VERSION,$row[compatible_version]))
					{
						$this->original = $row[template];
						$this->template	= $this->original;
					}
					else
					{
						$this->error = true;
						$this->messages[0][] = str_replace('<id>',$id,CABLE_ERROR_TEMPLATE_VERSION);
					}

					return null;
				}
				
				$this->error = true;
				$this->messages[0][] = str_replace('<id>',$id,CABLE_ERROR_TEMPLATE_MISSING);
				
				return null;
			}
			
			$this->error = mysql_error($db->db_link);
		}
		elseif (($config != false) && ($db === false) && ($theme != false) && ($id != false))
		{
			/* load the template directly from the file */
			$this->id = $id;
			
			if ($theme == '')
				$path = $this->config[SitePath]."themes/$id.tmpl";
			else
				$path = $this->config[SitePath]."themes/$theme/$id.tmpl";
			if (($this->original = file_get_contents($path)) === false)
				$this->messages[0][] = str_replace('<id>',$id,CABLE_ERROR_TEMPLATE_FILE);
			else
				$this->template = $this->original;
		}
		elseif (($config != false) && ($db === false) && ($theme === false) && ($id === false))
		{
			/* pseudo-overloading, the variable "$config" contains the template text */
			$this->original = $config;
			$this->template = $config;
		}
		else /* just initialize with empty templates */
		{
			$this->original = '';
			$this->template = '';
		}
		
		return null;
	}
	
	public	function ReplaceChild($id,$replace,$original=false)
	{
		if ($original)
			$tmpl = &$this->original;
		else
			$tmpl = &$this->template;

		$start_pos	= strpos($tmpl,'{'.$id.'}');
		$end_pos	= strpos($tmpl,'{/'.$id.'}') +strlen('{/'.$id.'}');
		$pre_string = substr($tmpl,0,$start_pos);
		$post_string = substr($tmpl,$end_pos);

		if (($start_pos === false) || ($end_pos === false))
		{
			$this->messages[1][] = str_replace('<id2>',$this->id,str_replace('<id1>',$id,CABLE_ERROR_TEMPLATE_CHILD));
			return false;
		}
		
		if ($original)
			$this->original = $pre_string.$replace.$post_string;
		else
			$this->template = $pre_string.$replace.$post_string;
		
		return true;
	}
	
	public	function GetChild($id)
	{
		if ((($start_pos = strpos($this->original,'{'.$id.'}')) === false) || (($length = strpos($this->original,'{/'.$id.'}')) === false))
		{
			$this->messages[1][] = str_replace('<id2>',$this->id,str_replace('<id1>',$id,CABLE_ERROR_TEMPLATE_CHILD));
			return false;
		}
		
		/* find start and length positions */
		$start_pos = $start_pos + strlen('{'.$id.'}');
		$length = $length - $start_pos;
		
		if ($length > 0)
		{
			$template = substr($this->original,$start_pos,$length);
			$child = new CABLEtemplate($template);
			$child->id = $id;
			return $child;
		}
		else
		{
			$this->messages[1][] = str_replace('<id2>',$this->id,str_replace('<id1>',$id,CABLE_ERROR_TEMPLATE_CHILD));
			return false;
		}
	}
	
	public	function ReplaceInsert($id,$replace)
	{
		if (strpos($this->template,'{I '.$id.' /}') !== false)
		{
			$this->template = str_replace('{I '.$id.' /}',$replace,$this->template);
			return $this->template;
		}
		else
		{
			$this->messages[2][] = str_replace('<id>',$id,CABLE_ERROR_TEMPLATE_INSERT);
			return false;
		}
	}
	
	/* these functions could result in long parsing times and shouldn't be used carelessly */
	public	function AppendInsert($id,$append)
	{
		return $this->ReplaceInsert($id,$append.'{I '.$id.' /}');
	}
	
	public	function PrependInsert($id,$prepend)
	{
		return $this->ReplaceInsert($id,'{I '.$id.' /}'.$prepend);
	}
		
	public	function SetBool($id,$value)
	{
		if ((bool)$value)
		{
			$new_template = str_replace('{B '.$id.'}','',$this->template);
			$new_template = str_replace('{/B '.$id.'}','',$new_template);
		}
		else
		{
			$new_template = str_replace('{B !'.$id.'}','',$this->template);
			$new_template = str_replace('{/B !'.$id.'}','',$new_template);
		}

		if($this->template != $new_template)
		{
			$this->template = $new_template;
			return true;
		}
		else
		{
			$this->messages[2][] = str_replace('<id>',$id,CABLE_ERROR_TEMPLATE_BOOL);
			return false;
		}
	}
	
	public	function GetValue($id,$force_array=false)
	{
		/* get matches for the basic value blocks */
		preg_match_all('/\{V '.$id.' "(.*?)" \/\}/',$this->template,$value_matches);
		$value_matches = $value_matches[1];

		/* if it's an array, return the entire array */
		if ((count($value_matches) > 1) || ($force_array))
			return $value_matches;
			
		if ($value_matches[0] == 'true')
			return true;
		elseif($value_matches[0] == 'false')
			return false;
		elseif($value_matches[0] == 'null')
			return null;
		else
			return $value_matches[0];
	}
	
	public	function Render()
	{
		/* get a list of all boolean and subtemplate block names */
		preg_match_all('/\{B (\S*?)\}/',$this->template,$bool_matches);
		$bool_matches = array_unique($bool_matches[1]);
		preg_match_all('/\{[\/](\S*?)\}/',$this->template,$sub_matches);
		$sub_matches = array_unique($sub_matches[1]);

		/* strip out matching blocks */
		foreach($bool_matches as $id)
			$this->template = preg_replace('/\{B '.$id.'\}.*?\{\/B '.$id.'\}/s','',$this->template);
		foreach($sub_matches as $id)
			$this->template = preg_replace('/\{'.$id.'\}.*?\{\/'.$id.'\}/s','',$this->template);

		/* strip insert blocks */
		$this->template = preg_replace('/\{I.*? \/\}/','',$this->template);
		
		/* strip value blocks */
		$this->template = preg_replace('/\{V.*? \/\}/','',$this->template);

		/* strip empty lines */
		$this->template = preg_replace('/[\n\r\v]\t*[\n\r\v]/',"\n",$this->template);
		
		return $this->template;
	}
	
	public	function Reset()
	{
		$this->template = $this->original;
	}

	public	function Cache()
	{
		$this->cache = $this->template;
	}
}

class CABLEplugin
{
	private	$name			= null;
	private	$plugin_config	= null; /* local plugin (from ini file) config */
	
	private	$user			= null;
	private	$db				= null;
	private	$config			= null; /* cable config */
	
	public	$connector		= null;
	public	$abort			= false;
	public	$error			= false;
	public	$messages		= array();
	
	public	function __construct($name,&$config,&$db,&$user)
	{		
		$this->name		= $name;
		
		/* store pointers to the base CABLE information classes */
		$this->user		= &$user;
		$this->db		= &$db;
		if (is_object($config))
			$this->config = &$config->values;
		elseif(is_array($config))
			$this->config = &$config;
	
		/* load the plugin configuration file */
		$plugin_path = "./plugins/$this->name/$this->name.php";
		$config_path = "./plugins/$this->name/$this->name.ini";

		if (is_readable($config_path))
			$this->plugin_config = parse_ini_file($config_path);
		else
		{
			$this->error = true;
			$this->messages[1][] = str_replace('<id>',$this->name,CABLE_ERROR_PLUGIN_CONFIG);
			return null;
		}
		
		if (!CABLE_checkVersion(CABLE_VERSION,$this->plugin_config['Compatible']))
		{
			$this->error = true;
			$this->messages[1][] = str_replace('<id>',$this->name,CABLE_ERROR_PLUGIN_VERSION);
			return null;
		}
		
		if (file_exists($plugin_path) && is_readable($plugin_path))
		{
			include_once($plugin_path);
			$this->messages[3][] = str_replace('<id>',$this->name,CABLE_PLUGIN_LOADED);
		}
		else
		{
			$this->error = true;
			$this->messages[1][] = str_replace('<id>',$this->name,CABLE_ERROR_PLUGIN_MISSING);
			return null;
		}

		return null;
	}
	
	public	function Connect(&$template=false,&$data=false)
	{
		/* trash the old connector */
		if ($this->connector)
			unset($this->connector);

		/* create the new one */
		if ($template && $data)
			$this->connector = new CABLEconnector(@array_merge($this->config,$this->plugin_config),$this->db,$this->user,$template,$data);
		else
			$this->connector = new CABLEconnector(@array_merge($this->config,$this->plugin_config),$this->db,$this->user);
			
		return true;
	}
	
	public	function Execute($function='')
	{			
		if ($function != '')
			$function = '_'.$function;
		
		/* if we have an uninitialized connector, crash and burn */
		if (!$this->connector)
		{
			$this->error = true;
			$this->messages[1][] = str_replace('<id>',$this->name.$function,CABLE_ERROR_PLUGIN_CONNECTOR);
			return false;
		}
			
		if (!function_exists($this->name.$function))
		{
			$this->error = true;
			$this->messages[1][] = str_replace('<id>',$this->name.$function,CABLE_ERROR_PLUGIN_NOLOAD);
			return false;
		}
		else
		{
			/* call plugin function */
			$ret = call_user_func($this->name.$function,$this->connector);
			
			/* set abort flag for plugin if connector calls for it */
			if ($this->connector->abort)
			{
				$this->messages[2][] = str_replace('<id>',$this->name,CABLE_PLUGIN_ABORT);
				$this->abort = true;
			}
			
			/* parse return value of plugin function */
			if ($ret === false)
				$this->messages[1][] = str_replace('<id>',$this->name.$function,CABLE_ERROR_PLUGIN_NOEXECUTE);
			elseif ($ret !== true)
				$this->messages[2][] = str_replace('<id1>',$this->name,str_replace('<id2>',$ret,CABLE_ERROR_PLUGIN_INTERNAL));
			return $ret;
		}
	}
}

class CABLEconnector
{
	public	$timestamp	= null;
	public	$user		= null;
	public	$db			= null;
	public	$db_link	= null;
	public	$config		= null;
	public	$template	= null;
	public	$data		= array();	/* specialized situation-specific construct */
	public	$abort		= false;
	
	public	function __construct(&$config,&$db,&$user,&$template=false,&$data=false)
	{
		$this->user			= &$user;
		$this->timestamp	= $this->user->timestamp;
		$this->db			= &$db;
		$this->db_link		= $this->db->db_link;
		$this->config		= &$config;
		if ($template)
			$this->template		= &$template;
		if ($data)
			$this->data			= &$data;

		return null;
	}
	
	public	function Abort($value=true)
	{
		$this->abort = $value;
	}
}

class CABLEcache
{
	private	$date		= null;
	private	$query		= null;
	private	$config;
	
	public	$name		= '';
	public	$body		= '';	
	public	$messages	= array();
	public	$error		= false;
	
	public	function __construct($name,&$config)
	{
		$this->name		= $name;
		$this->config	= &$config;
	}
	
	public	function GetCache($id)
	{
		/* returns cache content if not outdated, otherwise returns false */

		if ($id > (time() - $this->config->values[CacheTimeout]))
			$file_path = "./caches/$this->name.latest";
		else
			$file_path = "./caches/$this->name.$id";
		
		/* don't cache pages resulting from more than a single get or post variable */
		if ((count($_GET) > 1) || (count($_POST) > 1))
		{
			$this->messages[3][] = CABLE_CACHE_INAPPLICABLE;
			return false;
		}
		elseif (($_GET['t'] == '') && ($_POST['t'] == ''))
		{
			/* don't cache pages that have a get or post variable that isn't a timestamp */
			if (($_GET['t'] == '') && ($_POST['t'] == ''))
				return false;
		}
		
		if (file_exists($file_path))
		{		
			if ($temp = file($file_path,FILE_IGNORE_NEW_LINES))
			{
				$this->date = array_shift($temp);
				$this->query = array_shift($temp);
	
				/* cache is not applicable, discard */
				if ($this->query != $_ENV[QUERY_STRING])
				{
					$this->messages[3][] = CABLE_CACHE_INAPPLICABLE;
					return false;
				}
				
				/* cache is old, discard */
				if (($this->date + $this->config->values[CacheTimeout]) < time())
				{
					$this->messages[3][] = CABLE_CACHE_DISCARDED;
					return false;
				}
				
				$this->messages[3][] = CABLE_CACHE_READ;
				return implode("\n",$temp);
			}
			else
			{
				$this->messages[3][] = CABLE_CACHE_READERROR;
				return false;
			}
		}
		
		return false;
	}
	
	public	function WriteCache($id,$data)
	{
		/* writes data to cache file, returns true on success, otherwise false */
		if ($id < (time() - $this->config->values[CacheMemory]))
		{
			$this->messages[3][] = CABLE_CACHE_MEMORY;
			return false;
		}
		
		/* don't cache pages resulting from more than a single get or post variable */
		if ((count($_GET) > 1) || (count($_POST) > 1))
			return false;
		elseif ((count($_GET) == 1) || (count($_POST) == 1))
		{
			/* don't cache pages that have a get or post variable that isn't a timestamp */
			if (($_GET['t'] == '') && ($_POST['t'] == ''))
				return false;
		}
		
		if ($id > (time() - $this->config->values[CacheTimeout]))
			$file_path = "./caches/$this->name.latest";
		else
			$file_path = "./caches/$this->name.$id";
			
		if ($handle = @fopen($file_path,'w'))
		{
			if(flock($handle,LOCK_EX))
			{
				if (fwrite($handle,time()."\n"))
				{	
					fwrite($handle,$_ENV[QUERY_STRING]."\n");
					fwrite($handle,$data);
					$this->messages[3][] = CABLE_CACHE_WRITTEN;
				}
				else
					$this->messages[3][] = CABLE_ERROR_CACHE_WRITABLE;
					
				fclose($handle);
				@flock($handle,LOCK_UN);
			}
			else
			{
				$this->messages[3][] = CABLE_ERROR_CACHE_LOCK;
				return false;
			}
		}
		else
		{
			$this->messages[3][] = CABLE_ERROR_CACHE_WRITABLE;
			return false;
		}
	}
}

/* general functions */
function CABLE_MessageArrayCombine($array1,$array2)
{
	$new_array[0] = array_merge((array)$array1[0],(array)$array2[0]);
	$new_array[1] = array_merge((array)$array1[1],(array)$array2[1]);
	$new_array[2] = array_merge((array)$array1[2],(array)$array2[2]);
	$new_array[3] = array_merge((array)$array1[3],(array)$array2[3]);

	return $new_array;
}

function CABLE_MakeMessages($messages,$cutoff)
{
	$message_string = "<pre>\n";

	for($i=0; $i <= $cutoff; $i++)
	{
		if ($messages[$i])
		{
			foreach($messages[$i] as $message)
			{
				switch ($i)
				{
					case 0:
						$message_string	= $message_string."Critical Error: $message\n";
						break;
					case 1:
						$message_string	= $message_string."Error: $message\n";
						break;
					case 2:
						$message_string	= $message_string."Warning: $message\n";
						break;
					case 3:
						$message_string	= $message_string."Note: $message\n";
						break;
				}
					
			}
		}
	}
	return $message_string."\n</pre>\n";
}

function CABLE_checkVersion($required,$current)
{
	$pieces_r	= explode('.',$required);
	$pieces_c	= explode('.',$current);
	
	for ($i=0; $i<count($pieces_c); $i++)
		if ($pieces_c[$i] >= $pieces_r[$i])
			return true;
	
	return false;
}

?>